//
// Created by yuping.li on 10/17/24.
//

#ifndef DS18B20_DRIVER_HPP_
#define DS18B20_DRIVER_HPP_

#include "log.hpp"
#include "delay.hpp"

class Ds18b20Driver {
 public:

  typedef struct {
    GPIO_TypeDef* gpio_bank;
    uint16_t gpio_pin;
  } Ds18b20;
  
  Ds18b20Driver(GPIO_TypeDef* gpio_bank, uint16_t gpio_pin) {
    config.gpio_bank = gpio_bank;
    config.gpio_pin = gpio_pin;
  }

  uint8_t init() {
    reset(&config);
    return check(&config);
  }

  int16_t get_temperature() {
    uint8_t flag = 1;           /* 默认温度为正数 */
    uint8_t temp_reg, temp_reg_h;
    int16_t temp;

    start(&config);            /*  ds1820 start convert */
    reset(&config);
    check(&config);
    write_byte(&config, 0xcc);   /*  skip rom */
    write_byte(&config, 0xbe);   /*  convert */
    temp_reg = read_byte(&config);   /*  LSB */
    temp_reg_h = read_byte(&config);   /*  MSB */

    if (temp_reg_h > 7) {/* 温度为负，查看DS18B20的温度表示法与计算机存储正负数据的原理一致：
        正数补码为寄存器存储的数据自身，负数补码为寄存器存储值按位取反后+1
        所以我们直接取它实际的负数部分，但负数的补码为取反后加一，但考虑到低位可能+1后有进位和代码冗余，
        我们这里先暂时没有作+1的处理，这里需要留意 */
      temp_reg_h = ~temp_reg_h;
      temp_reg = ~temp_reg;
      flag = 0;   /* 温度为负 */
    }

    temp = temp_reg_h;      /* 获得高八位 */
    temp <<= 8;
    temp += temp_reg;     /* 获得底八位 */

    /* 转换成实际温度 */
    if (flag == 0) {   /* 将温度转换成负温度，这里的+1参考前面的说明 */
      temp = (double) (temp + 1) * 0.625;
      temp = -temp;
    } else {
      temp = (double) temp * 0.625;
    }

    return temp;
  }

 protected:
  Ds18b20 config{};

  static uint8_t read_byte(const Ds18b20 *config) {
    uint8_t i, b, data = 0;

    for (i = 0; i < 8; i++) {
      b = read_bit(config); /* DS18B20先输出低位数据 ,高位数据后输出 */

      data |= b << i;         /* 填充data的每一位 */
    }

    return data;
  }

  static uint8_t read_bit(const Ds18b20 *config) {
    uint8_t data = 0;
    write_gpio_value(config, 0);
    Delay::instance().delay_us(2);
    write_gpio_value(config, 1);
    Delay::instance().delay_us(12);

    if (read_gpio_value(config)) {
      data = 1;
    }

    Delay::instance().delay_us(50);
    return data;
  }

  static void write_byte(const Ds18b20 *config, uint8_t data) {
    uint8_t j;

    for (j = 1; j <= 8; j++) {
      if (data & 0x01) {
        write_gpio_value(config, 0);  /*  Write 1 */
        Delay::instance().delay_us(2);
        write_gpio_value(config, 1);
        Delay::instance().delay_us(60);
      } else {
        write_gpio_value(config, 0);  /*  Write 0 */
        Delay::instance().delay_us(60);
        write_gpio_value(config, 1);
        Delay::instance().delay_us(2);
      }

      data >>= 1;             /* 右移,获取高一位数据 */
    }
  }

  static uint8_t read_gpio_value(const Ds18b20 *config) {
    return HAL_GPIO_ReadPin(config->gpio_bank, config->gpio_pin);
  }

  static void write_gpio_value(const Ds18b20 *config, uint8_t value) {
    value ? \
  HAL_GPIO_WritePin(config->gpio_bank, config->gpio_pin, GPIO_PIN_SET) : \
  HAL_GPIO_WritePin(config->gpio_bank, config->gpio_pin, GPIO_PIN_RESET); \

  }

  static void reset(const Ds18b20 *config) {
    write_gpio_value(config, 0);  /* 拉低DQ,复位 */
    Delay::instance().delay_us(750);      /* 拉低750us */
    write_gpio_value(config, 1);  /* DQ=1, 释放复位 */
    Delay::instance().delay_us(15);       /* 延迟15US */
  }

  static void start(const Ds18b20 *config) {
    reset(config);
    check(config);
    write_byte(config, 0xcc);   /*  skip rom */
    write_byte(config, 0x44);   /*  convert */
  }

  static uint8_t check(const Ds18b20 *config) {
    uint8_t retry = 0;
    uint8_t rval = 0;

    while (read_gpio_value(config) && retry < 200) {
      retry++;
      Delay::instance().delay_us(1);
    }

    if (retry >= 200) {
      rval = 1;
    } else {
      retry = 0;

      while (!read_gpio_value(config) && retry < 240) {
        retry++;
        Delay::instance().delay_us(1);
      }

      if (retry >= 240) rval = 1;
    }

    return rval;
  }

};

#endif //DS18B20_DRIVER_HPP_
